package com.hmall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.hmall.common.dto.PageDTO;
import com.hmall.search.constant.EsConstant;
import com.hmall.search.pojo.ItemDoc;
import com.hmall.search.pojo.RequestParams;
import com.hmall.search.service.SearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 搜索实现类
 *
 * @Author Eeyore
 * @Date 2022/8/10 17:31
 * @Description: 搜索实现类
 */
@Service
@Slf4j
public class ImplSearchService implements SearchService {

    @Autowired
    RestHighLevelClient client;

    @Override
    public List<String> getSuggestion(String key) {
        try {
            //准备request
            SearchRequest request = new SearchRequest(EsConstant.ITEM_INDEX);

            //准备DSL
            request.source().suggest(
                    new SuggestBuilder()
                            .addSuggestion(
                                    "item_suggest",
                                    new CompletionSuggestionBuilder("suggestion")
                                            .prefix(key)
                                            .size(10)
                            )
            );

            //发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //解析结果
            List<String> resultList = new ArrayList<>();
            CompletionSuggestion suggestion = response.getSuggest().getSuggestion("item_suggest");
            List<CompletionSuggestion.Entry.Option> options = suggestion.getOptions();

            for (CompletionSuggestion.Entry.Option option : options) {
                String text = option.getText().toString();
                resultList.add(text);
            }
            return resultList;

        } catch (IOException e) {
            e.printStackTrace();
        }


        return new ArrayList<>();
    }

    /**
     * 查询条件过滤
     *
     * @param params
     * @return
     */
    @Override
    public Map<String, List<String>> filters(RequestParams params) {

        try {
            SearchRequest request = new SearchRequest(EsConstant.ITEM_INDEX);

            //过滤条件
            buildBasicQuery(params, request);

            //聚合
            //分类聚合
            aggByName(request, "categoryAgg", "category", 10);
            //品牌聚合
            aggByName(request, "brandAgg", "brand", 10);

            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //处理结果
            Map<String, List<String>> resultMap = new HashMap<>();
            //分类
            List<String> categoryList = getListByAggName(response, "categoryAgg");
            resultMap.put("category", categoryList);
            //品牌
            List<String> brandList = getListByAggName(response, "brandAgg");
            resultMap.put("brand", brandList);
            //价格
            List<String> priceList = getListByAggName(response, "priceAgg");
            resultMap.put("price", priceList);

        } catch (IOException e) {
            e.printStackTrace();
        }

        return new HashMap<>();
    }

    /**
     * 根据聚合名称获取结果【集合】
     *
     * @param response
     * @param aggName
     * @return
     */
    private List<String> getListByAggName(SearchResponse response, String aggName) {

        List<String> list = new ArrayList<>();
        Aggregations aggregations = response.getAggregations();

        Terms brandAgg = aggregations.get(aggName);
        List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();

        for (Terms.Bucket bucket : brandAggBuckets) {
            String key = bucket.getKeyAsString();
            list.add(key);
        }
        return list;
    }


    /**
     * 桶聚合条件
     *
     * @param request
     * @param aggName   聚合名称
     * @param fieldName 聚合字段
     * @param size      获取条数
     */
    private void aggByName(SearchRequest request, String aggName, String fieldName, Integer size) {
        request.source()
                .size(0)
                .aggregation(
                        AggregationBuilders
                                .terms(aggName)
                                .field(fieldName)
                                .size(size)
                );
    }

    /**
     * 分页查询 & 高亮显示
     *
     * @param params
     * @return
     */
    @Override
    public PageDTO<ItemDoc> getList(RequestParams params) {

        try {
            //准备request
            SearchRequest request = new SearchRequest(EsConstant.ITEM_INDEX);

            //准备DSL
            //buildBasicQuery
            buildBasicQuery(params, request);

            //排序
            String sortBy = params.getSortBy();
            if ("sold".equals(sortBy)) {
                request.source().sort("sold", SortOrder.DESC);
            } else if ("price".equals(sortBy)) {
                request.source().sort("price", SortOrder.DESC);
            }

            //分页
            Integer size = params.getSize();
            Integer page = params.getPage();
            request.source()
                    .from((page - 1) * size).size(size)
                    .highlighter(new HighlightBuilder()
                            .field("name")
                            .requireFieldMatch(false));

            //发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //解析响应
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private PageDTO handleResponse(SearchResponse response) {
        //解析响应
        SearchHits searchHits = response.getHits();
        //获取总条数
        Long total = searchHits.getTotalHits().value;
        //文档数组
        SearchHit[] hits = searchHits.getHits();
        //遍历
        List<ItemDoc> items = new ArrayList<>();
        for (SearchHit hit : hits) {
            //获取文档
            String json = hit.getSourceAsString();
            //反序列化
            ItemDoc itemDoc = JSON.parseObject(json, ItemDoc.class);

            //获取高亮字段的值
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();

            if (highlightFields != null && highlightFields.size() > 0) {
                HighlightField highlightField = highlightFields.get("name");

                if (highlightField != null) {
                    //获取高亮字段name的值，并转成字符串
                    String name = highlightField.getFragments()[0].toString();
                    //放入集合
                    itemDoc.setName(name);
                }
            }
            items.add(itemDoc);
        }
        //封装返回
        return new PageDTO(total, items);
    }


            /**
             * 封装查询条件
             *
             * @param params
             * @param request
             */
            private void buildBasicQuery (RequestParams params, SearchRequest request){
                //构建BooleanQuery
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

                //关键字搜索
                if (StringUtils.isNotBlank(params.getKey())) {
                    boolQuery.must(QueryBuilders.matchQuery("all", params.getKey()));
                } else {
                    boolQuery.must(QueryBuilders.matchAllQuery());
                }

                //分类
                if (StringUtils.isNotBlank(params.getCategory())) {
                    boolQuery.must(QueryBuilders.matchQuery("category", params.getCategory()));
                }
                //品牌
                if (StringUtils.isNotBlank(params.getBrand())) {
                    boolQuery.must(QueryBuilders.matchQuery("brand", params.getBrand()));
                }
                //价格
                if (params.getMinPrice() != null && params.getMaxPrice() != null) {
                    boolQuery.filter(QueryBuilders
                            .rangeQuery("price")
                            .gte(params.getMinPrice())
                            .lte(params.getMaxPrice())
                    );
                }
                request.source().query(boolQuery);
            }
        }